<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>10.4 控制输出格式和结构 - Langchain框架教程</title>
    <link rel="stylesheet" href="../shared_styles.css">
</head>
<body>
    <div class="container">
        <h1> 第十章：AI系统提示词设计进阶</h1>
        <section id="s10-4">
            <h2>10.4 控制输出格式和结构</h2>
            <p>在许多AI应用场景中，我们不仅需要LLM生成文本内容，还需要其输出遵循特定的格式或结构，以便于后续的程序处理或用户阅读。例如，我们可能需要LLM输出JSON对象、Markdown列表、HTML片段，或者按照固定的模板填充信息。通过精心设计的提示词，我们可以有效地控制LLM的输出格式和结构。</p>

            <h3>为什么需要控制输出格式？</h3>
            <ul>
                <li><strong>程序化处理：</strong> 当LLM的输出需要作为另一个程序的输入时（例如，提取信息后存入数据库，或将结构化数据用于生成图表），固定的格式（如JSON、XML、CSV）是必不可少的。</li>
                <li><strong>提高可读性：</strong> 对于用户而言，结构化的输出（如列表、表格、分段）比一大段连续文本更容易理解和消化。</li>
                <li><strong>确保一致性：</strong> 在批量处理或多次交互中，保持输出格式的一致性对于用户体验和系统稳定性至关重要。</li>
            </ul>

            <h3>控制输出格式的提示词策略</h3>
            <p>以下是一些通过提示词引导LLM控制输出格式和结构的有效策略：</p>

            <ol>
                <li>
                    <strong>明确指定期望的格式 (Explicitly Specify the Desired Format):</strong>
                    <p>在系统提示词中清晰地说明你期望的输出格式。这是最直接也是最有效的方法。</p>
                    <pre><code class="language-python">
# (接上一节的Qwen LLM初始化代码)
# from langchain_openai import ChatOpenAI
# from langchain_core.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate
# from langchain_core.output_parsers import StrOutputParser
# import os
#
# api_key = os.environ.get("DASHSCOPE_API_KEY")
# qwen_llm = ChatOpenAI(
#     base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
#     api_key=api_key,
#     model="qwen-plus",
#     temperature=0.1
# )

system_prompt_json = """
你是一个信息提取助手。请从用户提供的文本中提取关键信息，并以JSON格式输出。
JSON对象应包含以下字段：
- "product_name": 产品名称 (string)
- "price": 产品价格 (number)
- "currency": 货币单位 (string, 例如 "CNY", "USD")
- "in_stock": 是否有库存 (boolean)

如果某个信息在文本中找不到，请将对应字段的值设为 null。
"""

prompt_template_json = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template(system_prompt_json),
    HumanMessagePromptTemplate.from_template("用户文本：{user_text}")
])

json_chain = prompt_template_json | qwen_llm | StrOutputParser()

# 测试提取信息并输出JSON
user_text_json = "我们最新款的智能手机，型号为X1，售价为人民币3999元，目前库存充足。"
response_json = json_chain.invoke({"user_text": user_text_json})
print(f"用户文本: {user_text_json}")
print(f"AI输出 (JSON): {response_json}")

# 测试信息不完整的文本
user_text_incomplete = "那款笔记本电脑，价格是8800美元，库存情况未知。"
response_incomplete = json_chain.invoke({"user_text": user_text_incomplete})
print(f"用户文本: {user_text_incomplete}")
print(f"AI输出 (JSON): {response_incomplete}")
                    </code></pre>
                    <p>在提示词中详细描述JSON的结构（字段名、数据类型）有助于LLM生成符合要求的输出。</p>
                </li>
                <li>
                    <strong>提供格式示例 (Provide Format Examples):</strong>
                    <p>除了文字描述，提供一个或多个期望输出格式的示例是极其有效的。LLM非常擅长模仿给定的模式。</p>
                    <pre><code class="language-python">
system_prompt_markdown = """
你是一个总结助手。请将用户提供的文本总结为几个关键点，并以Markdown格式的无序列表输出。
示例格式：
- 关键点1
- 关键点2
- 关键点3
...
"""

prompt_template_markdown = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template(system_prompt_markdown),
    HumanMessagePromptTemplate.from_template("用户文本：{user_text}")
])

markdown_chain = prompt_template_markdown | qwen_llm | StrOutputParser()

# 测试总结并输出Markdown列表
user_text_markdown = """
Langchain是一个用于开发基于语言模型的应用程序的框架。
它的核心思想是将复杂的LLM调用过程分解为可组合的组件。
主要模块包括Models, Prompts, Chains, Agents, Memory, Indexes等。
使用Langchain可以更方便地构建问答系统、聊天机器人、文本生成应用等。
"""
response_markdown = markdown_chain.invoke({"user_text": user_text_markdown})
print(f"用户文本:\n{user_text_markdown}")
print(f"AI输出 (Markdown列表):\n{response_markdown}")
                    </code></pre>
                    <p>通过示例，LLM能更直观地理解所需的格式。</p>
                </li>
                <li>
                    <strong>使用结构化输出解析器 (Use Structured Output Parsers):</strong>
                    <p>Langchain提供了多种Output Parser，它们可以与提示词结合使用，强制LLM以特定格式输出，并在接收到输出后进行解析和验证。这是一种更健壮的处理方式，尤其适用于需要严格格式的场景（如JSON）。</p>
                    <p>例如，使用<code>PydanticOutputParser</code>可以定义一个Pydantic模型来描述期望的输出结构，Langchain会自动生成相应的提示词并解析输出。</p>
                    <pre><code class="language-python">
# (接上一节的Qwen LLM初始化代码)
# from langchain_openai import ChatOpenAI
# from langchain_core.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate
# from langchain_core.output_parsers import StrOutputParser
# import os
#
# api_key = os.environ.get("DASHSCOPE_API_KEY")
# qwen_llm = ChatOpenAI(
#     base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
#     api_key=api_key,
#     model="qwen-plus",
#     temperature=0.1
# )

from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.pydantic_v1 import BaseModel, Field
from typing import List

# 定义期望的输出结构
class Recipe(BaseModel):
    name: str = Field(description="食谱名称")
    ingredients: List[str] = Field(description="所需食材列表")
    steps: List[str] = Field(description="制作步骤列表")
    prep_time_minutes: int = Field(description="准备时间（分钟）")
    cook_time_minutes: int = Field(description="烹饪时间（分钟）")

# 创建PydanticOutputParser实例
parser = PydanticOutputParser(pydantic_object=Recipe)

system_prompt_recipe = """
你是一个食谱生成助手。请根据用户提供的菜名，生成一个详细的食谱。
请严格按照以下格式要求输出：
{format_instructions}
"""

prompt_template_recipe = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template(system_prompt_recipe),
    HumanMessagePromptTemplate.from_template("请生成一个关于{dish_name}的食谱。")
]).partial(format_instructions=parser.get_format_instructions()) # 将格式指令注入提示词

recipe_chain = prompt_template_recipe | qwen_llm | parser # 将解析器添加到链的末尾

# 测试生成食谱
dish = "番茄炒蛋"
try:
    recipe = recipe_chain.invoke({"dish_name": dish})
    print(f"生成的食谱 (Pydantic对象):\n{recipe}")
    print(f"食谱名称: {recipe.name}")
    print(f"主要食材: {recipe.ingredients}")
except Exception as e:
    print(f"生成食谱失败: {e}")

                    </code></pre>
                    <p>使用Output Parser可以大大提高输出格式的可靠性，即使LLM的原始输出略有偏差，解析器也可能进行修正或报错，从而更容易发现问题。</p>
                </li>
                <li>
                    <strong>使用分隔符 (Use Delimiters):</strong>
                    <p>在提示词中指定特定的分隔符（如<code>---</code>, <code>###</code>, <code><response></response></code>）来标记输出的不同部分或结构元素，有助于LLM组织其输出。</p>
                    <p>例如：“请将你的回答分为两部分，用<code>---</code>分隔。第一部分是总结，第二部分是详细解释。”</p>
                </li>
                <li>
                    <strong>迭代和修正 (Iterate and Refine):</strong>
                    <p>控制输出格式可能需要多次尝试。如果LLM第一次没有按照期望的格式输出，可以根据其输出调整提示词，使其更明确或提供更好的示例，然后再次尝试。</p>
                </li>
            </ol>

            <h3>总结</h3>
            <p>通过明确指定格式、提供示例、利用结构化输出解析器、使用分隔符等多种策略，我们可以有效地引导LLM生成符合特定格式和结构的输出。这对于构建能够与外部系统集成或提供良好用户体验的AI应用至关重要。Langchain的Output Parser提供了强大的工具来辅助实现这一目标，使得控制LLM输出格式变得更加可靠和便捷。</p>
        </section>

        <div class="navigation">
            <a href="../../langchain_tutorial_outline.html">返回教程目录</a>
            <a href="../chapter10/chapter10_3_ambiguity_uncertainty.html">&larr; 上一节：10.3 处理歧义和不确定性的提示词策略</a>
            <a href="../chapter10/chapter10_5_few_shot_prompting.html">下一节：10.5 Few-shot Prompting（少样本提示） &rarr;</a>
        </div>
    </div>
</body>
</html>
